#include "diagnostics.h"
#include "debugger.h"
#include "model.h"
#include "preprocessor.h"
#include <vector>
#include "integrator.h"

// global default stream
qstream STDOUT(stdout);

/* ----------------------------------------------------------------------------------->

   class debugger::local 

   Provides interface for debugger functions that do not require global scope
   definitions.

   < --------------------------------------------------------------------------------- */
bool matchstring(const string & input, const char * command){
    size_t i = 0; // function matches two strings if they match up to length of the smallest
    if(strlen(command) < input.size()) return false;
    while( i < input.size() && command[i] != 0 ){
        if( input[i] != command[i] ) return false;
        i++;
    }
    return true;
}

bool iscommandinlist_aux(const string & input, ...) // global scope now
{
    va_list ap;
    int counter = 0;
    va_start(ap, input);
    while( true ) {
        const char * strn = va_arg(ap, const char *);
        if( strn == NULL ) return false;
        if( matchstring(input,strn) ) return true;
        if( counter > 50 ) fatal_stop("Function 'iscommandinlist_aux' was improperly constructed. Loop appears to be continuous.\n");
        counter++;
    }
}
// ---------------------------------------------------------------------------------
class debugger::local {
    debugger * parent;
    int cinstr;
    model_diagnostics_block * diagblock;
  public:
    explicit local(); // no default constructor
    explicit local(local&); // no implicit copy constructor

    local(debugger * cdbg, model_diagnostics_block * dbl, int pos) : parent(cdbg), cinstr(pos), diagblock(dbl)
    {}

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    void write(const mup::IValue & val) const
    {
        const qstream & dbout = parent->getDBGOUT();
        dbout << val;
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    void write(const char * fmt, ...) const 
    {
        va_list(arg);
        va_start(arg, fmt);
        const qstream & dbout = parent->getDBGOUT();
        dbout.vprintf(fmt, arg);
    } 
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void say(const char * fmt, ...) const 
    {
        va_list(arg);
        va_start(arg, fmt);
        const qstream & dbout = parent->getDBGOUT();
        dbout.printf(" <==  ");
        dbout.vprintf(fmt, arg);
        dbout.printf("\n");
    }


    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void printlines( const string & prefix, const string & input, int sline, int eline, int starredline=-1 )
    {
        const qstream & dbout = parent->getDBGOUT();
        int ln = 1;
        for(size_t pos = 0; pos < input.size(); ++pos ){
            if( ln >= sline && ln <= eline ){
                dbout.putc( input[pos] );
            }
            if( input[pos] == '\n' ){ 
                ln ++;
                if( ln > eline ) return;
                if( ln >= sline ){
                    if(starredline == ln){
                        this->write("%s:%i\t*", prefix.c_str(), ln);
                    }else{
                        this->write("%s:%i\t ", prefix.c_str(), ln);
                    }
                }
            }
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void printdebuglineinformation(int deltalines)
    {
        vector< model_diagnostics_block::instruction * > & instructions = this->diagblock->instructions;
        const int sln = instructions[cinstr]->debug_attributes.slinenumber;
        const int eln = instructions[cinstr]->debug_attributes.elinenumber;
        const int nid = instructions[cinstr]->debug_attributes.namelist;
        if(sln < 0 || eln < 0) return;
        preprocessordata & pdata = diagblock->mo->preprocdata;
        namelistdata & ndata = *(pdata.namelists[nid]);
        const string & prefix = ndata.filename;
        int starredline = -1;
        if( deltalines > 0 ) starredline = sln;
        printlines( prefix, ndata.filecontent, sln-deltalines, eln+deltalines, starredline );
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
#define iscommandinlist(keyword, ...) iscommandinlist_aux(keyword,## __VA_ARGS__ , NULL) 
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void evalparser(const token_list & expr, ParserX & P)
    {
        if(expr.size() > 0){
            P.SetExpr(expr);
        }// else use what is inside the parser
        try{
            const IValue & val = P.Eval();
            write(" <==  ");
            write(val);
            write("\n");
        }catch(const ParserError & e){
            say("%s", e.what() );
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool interpretbreakpointadd(const token_list & tokens)
    {
        bool arg1isinteger = (tokens.size() == 2) && isStringInteger(tokens[1].c_str());
        bool breakfunc = (tokens.size() == 2) && !arg1isinteger;
        bool breakcurrentfile = (!breakfunc && tokens.size() == 2 && arg1isinteger);
        bool breakfile = ! breakfunc && !breakcurrentfile && tokens.size() >= 2;
        if(breakfunc){
            const char * funcname = tokens[1].c_str();
            if(parent->setbreakpointbyfunction(funcname)){
                say("Breakpoint set @ function '%s'.", funcname); return true;
            }else{
                say("Cannot set breakpoint: No function '%s' exists.", funcname); return false;
            }
        }else if(breakcurrentfile || breakfile){
            if( breakfile && ! isStringInteger( tokens[2].c_str() ) ){
                say("Expect an integer line number following the filename."); return false;
            }
            int linenum = breakcurrentfile ? convertstring2integer( tokens[1] ) : convertstring2integer( tokens[2] );
            const char * fname = NULL;
            if(breakcurrentfile){
                int nid = this->diagblock->instructions[cinstr]->debug_attributes.namelist;
                fname = this->parent->mo->preprocdata.namelists[nid]->filename.c_str();
            }else{
                fname = tokens[1].c_str();
            }
            if(parent->setbreakpointbyfile(fname, linenum)){
                say("Breakpoint set in file '%s' at line '%i'.", fname, linenum); return true;
            }else{
                say("Cannot set breakpoint in file '%s' at line '%i'.", fname, linenum); return false;
            } 
        }else{
            say("Expect arguments of the form either 1) function name, 2) linenumber, 3) filename linenumber.");
            return false;
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool interpretbreakpointdelete(const token_list & tokens)
    {
        bool removeall = (tokens.size() == 2) && (tokens[1] == "all");
        bool arg1isinteger = (tokens.size() == 2) && isStringInteger(tokens[1].c_str());
        bool breakfunc = (tokens.size() == 2) && !arg1isinteger;
        bool breakcurrentfile = (!breakfunc && tokens.size() == 2 && arg1isinteger);
        bool breakfile = ! breakfunc && !breakcurrentfile && tokens.size() >= 2;
        if(removeall){
            say("Removing all breakpoints.");
            parent->removebreakpoint();
            parent->removeallbreakpoints();
            return true;
        }else if(breakfunc){
            const char * funcname = tokens[1].c_str();
            if(parent->removebreakpointbyfunction(funcname)){
                say("Breakpoint removed @ function '%s'.", funcname); return true;
            }else{
                say("No breakpoint removed."); return false;
            }
        }else if(breakcurrentfile || breakfile){
            if( breakfile && ! isStringInteger( tokens[2].c_str() ) ){
                say("Expect an integer line number following the filename."); return false;
            }
            int linenum = breakcurrentfile ? convertstring2integer( tokens[1] ) : convertstring2integer( tokens[2] );
            const char * fname = NULL;
            if(breakcurrentfile){
                int nid = this->diagblock->instructions[cinstr]->debug_attributes.namelist;
                fname = this->parent->mo->preprocdata.namelists[nid]->filename.c_str();
            }else{
                fname = tokens[1].c_str();
            }
            if(parent->removebreakpointbyfile(fname, linenum)){
                say("Breakpoint removed in file '%s' at line '%i'.", fname, linenum); return true;
            }else{
                say("Cannot remove breakpoint in file '%s' at line '%i'.", fname, linenum); return false;
            } 
        }else{
            say("Expect arguments of the form either 1) function name, 2) linenumber, 3) filename linenumber, 4) all to remove all breakpoints");
            return false;
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void printdiagvariable(const IValue & value, bool ishandle) const
    {
        if( ishandle && is_value_valid_handle(value) ){
            this->write("FILE@%p", reinterpret_cast<void*>(static_cast<int>(value.GetInteger())));
            return;
        }
        switch(value.GetType())
        {
          case 'i':
            this->write("%i", value.GetInteger()); break;
          case 'f':
            this->write("%f", value.GetFloat()); break;
          case 'c':
            this->write("(%f, %f)", value.GetFloat(), value.GetImag()); break;
          case 'a':
            this->write("ARRAY::size=%i", static_cast<int>(value.GetArray().size())); break;
          case 's':
            this->write("\"%s\"", value.GetString().c_str());
          case 'b':
            value.GetBool() ? this->write("true") : this->write("false");
          default:
            this->write("unknown");
        };
    };
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void probevarstack(const model_diagnostics_block * diagblock) const
    {
        this->say("CURRENT LIST:");
        int umax = diagblock->function_attributes.numargs >= 0 ? diagblock->function_attributes.numargs : 0;
        for(int u = 0; u < diagblock->variables.size(); u++){
            this->write( " [%i] @%p %s %s = ", u, &(diagblock->variables[u].data), get_type_id(diagblock->variables[u].dtype), diagblock->variables[u].name.c_str());
            printdiagvariable( *(diagblock->variables[u].data), diagblock->variables[u].dtype == 'h' );
            this->write("\n");
        } 

        if( ! diagblock->isblockfunction() ) return;
        const vector< vector<Value> * > & savedvars = diagblock->function_attributes.saved_variables;
        for(int u = savedvars.size()-1; u >= 0; u-- ){
            this->write("\tSAVED BLOCK:\n");
            const vector<Value> & vlist = *( savedvars[u] );
            for(int i = 0; i < vlist.size(); i++){
                this->write( "\t storedvar[%i] '%s' = ", i, diagblock->variables[i].name.c_str());
                printdiagvariable( vlist[i], false );
                this->write( "\n");
            }
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void displayparserconstants( const ParserX * Pptr ) const
    {
        if( ! Pptr ){
            this->say("No information in current parser."); 
            return;
        }
        this->say("Printing parser constants: ");
        const ParserX & P = *Pptr;
        val_maptype cmap = P.GetConst();
        for(val_maptype::const_iterator item = cmap.begin(); item != cmap.end(); ++item){
            const string & name = item->first;
            this->write("\t %s = ", name.c_str());
            this->write(*((*(item->second)).AsIValue()));
            this->write("\n"); 
        }
    } 
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void displayblockvariablestack(const string & blockname) const {
        // fetch the block
        const vector<model_diagnostics_block*> & dblocks = this->parent->mo->mdiags;
        this->say("Looking up variable stack information for block '%s'.", blockname.c_str());
        for(size_t iter = 0; iter < dblocks.size(); ++iter){
            if( dblocks[iter]->isblockfunction() && dblocks[iter]->attributes.name == blockname ){
                probevarstack( dblocks[iter] );
                return;
            }
        }
        this->say("No function or block '%s' exists.", blockname.c_str());
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void printfunctioncallinformation(const model_diagnostics_block * dblock, int depth = -1) const 
    {
        this->write("\t%s(", dblock->attributes.name.c_str());
        const int numargs = dblock->isblockfunction() ? dblock->function_attributes.numargs : 0;
        bool nested = false;
        if( numargs > 0 ){
            if(depth >= 0 && depth < dblock->function_attributes.saved_variables.size()) nested = true;
            vector<const Value *> values(numargs, (const Value*)NULL);
            if( nested ){
                const vector<Value> & vlist = *(dblock->function_attributes.saved_variables[depth]);
                for(size_t i = 0; i < numargs; i++) values[i] = &( vlist[i] );
            }else{ // just use top variables
                for(size_t i = 0; i < numargs; i++) values[i] = dblock->variables[i].data;
            }
            for(int i = 0; i < numargs; i++){
                this->write("%s %s=", get_type_id(dblock->variables[i].dtype), dblock->variables[i].name.c_str());
                printdiagvariable(*(values[i]), dblock->variables[i].dtype == 'h');
                if(i < numargs-1) this->write(", ");
            }
        }
        this->write(")");
        if( (! nested ) && dblock->isblockfunction() ){ // print depth
            this->write(" depth=%i", dblock->function_attributes.depth-1 );
        }
        if( ! dblock->isblockfunction() ){ // print file and line number
            preprocessordata & pdata = dblock->mo->preprocdata;
            int nid=dblock->instructions[0]->debug_attributes.namelist;
            int ln =dblock->instructions[0]->debug_attributes.slinenumber;
            namelistdata & ndata = *(pdata.namelists[nid]);
            const string & name = ndata.filename;
            this->write(" %s:%i", name.c_str(), ln); 
        }
        this->write("\n");
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    void printparservarstackinfo(ParserX & p) const
    {
        var_maptype vmap = p.GetVar();
        say("Printing variable token information for current parser:");
        for (var_maptype::iterator item = vmap.begin(); item != vmap.end(); ++item){
            Variable & var = *dynamic_cast<Variable *>( ((item->second)).Get() );
            string name = item->first;
            this->write(" \t%s @%p = ", name.c_str(), &var );
            printdiagvariable(*(var.AsIValue()), false);
            this->write("\n");
        }
    } 
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void printfunctionstack() const 
    {
        say("FUNCTION CALL STACK: ");
        for( int iter = static_cast<int>(parent->cdiag.size()) - 1; iter >= 0; iter--){
            const model_diagnostics_block * dblock = parent->cdiag[iter];
            int depth = -1;
            for(int x = 0; x <= iter; x++){
                if(parent->cdiag[x] == dblock) depth++;
            }
            printfunctioncallinformation(dblock, depth);
        }
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool interpretdisplaystackcommand(const token_list & tokens)
    {
        if( tokens.size() < 3 ){
            this->say("which stack?" );
            return false;
        }
        const string & keyword = tokens[2];
        if(iscommandinlist(keyword, "call", "functions")){
            printfunctionstack();
        }else if(iscommandinlist(keyword, "parser")){
            model_diagnostics_block::instruction * instruct = ( diagblock->instructions[cinstr] );
            printparservarstackinfo(*(instruct->parser));
        }else if(iscommandinlist(keyword, "variable")){
            // display all variables
            if( tokens.size() > 3 ){
                displayblockvariablestack(tokens[3]);
            }else{ 
                printfunctioncallinformation(diagblock);
                int umax = diagblock->function_attributes.numargs >= 0 ? diagblock->function_attributes.numargs : 0;
                for(int u = static_cast<int>(diagblock->variables.size())-1; u >= umax ; u--){
                    this->write( "\t [%i] %s %s = ", u, get_type_id(diagblock->variables[u].dtype), diagblock->variables[u].name.c_str());
                    printdiagvariable( *(diagblock->variables[u].data), diagblock->variables[u].dtype == 'h' );
                    this->write( "\n");
                } 
            }
        }else{
            say("Do not know how to display stack '%s'.", keyword.c_str());
            return false;
        }
        return true;
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool interpretbreakpointdisplaycommand(const token_list & tokens)
    {
        if( tokens.size() < 2 ){
            say("display what?");
            return false;
        }else{
            const string & target = tokens[1];
            if(iscommandinlist(target, "instruction")){
                say("NEXT INSTRUCTION  = '%s'", diagblock->instructions[cinstr]->getname());
            }else if(iscommandinlist(target, "expression")){
                model_diagnostics_block::instruction * instruct = ( diagblock->instructions[cinstr] );
                if(instruct->parser){
                    say("PARSER EXPRESSION = '%s'", instruct->parser->GetExprAsString().c_str());
                }else{
                    say("INSTRUCTION HAS NO EXPRESSION ");
                }
            }else if(iscommandinlist(target, "functions")){
                model_diagnostics_block::instruction & instruct = *(diagblock->instructions[cinstr]);
                if(! instruct.parser ){ say("No functions are available at the current instruction."); return false; }
                ParserX & P = *(instruct.parser);
                const fun_multimap & functions = P.GetFunDef();
                fun_multimap::const_iterator item = functions.begin();
                for( ; item != functions.end(); ++item){
                    const char * descr = item->second.Get()->AsICallback()->GetDesc();
                    if(strcmp(descr, "user defined diagnostics function") != 0 ){
                        write("%s\n", descr);
                    }
                }
                // now do user defined functions
                vector< const model_diagnostics_block * > func_list;
                parent->mo->getfunctiondiaglist( func_list ); // retrieve pointers to diag blocks that are functions
                for(size_t iter = 0; iter < func_list.size(); iter++){
                    const model_diagnostics_block & func = *(func_list[iter]);
                    vector<char_type> cstack = func.GetCallStackTypes();
                    char_type rtype = func.GetReturnType();
                    int numargs = func.getfuncnumargs();
                    const char * name = func.getfuncname().c_str();
                    write(" %s %s(", get_type_id(rtype), name);
                    for(int i = 0; i < numargs; i++){
                        write("%s %s", get_type_id(cstack[i]), func.variables[i].name.c_str());
                        if( i < numargs - 1) write(", ");
                    }
                    write(")\n");
                }
            }else if(iscommandinlist(target, "file")){
                if( tokens.size()  < 3 ){ say("Which file?"); return false; }
                const string & filename = tokens[2];
                preprocessordata & pdata = parent->mo->preprocdata;
                for(int iter = 0; iter < pdata.namelists.size(); iter++){
                    if( pdata.namelists[iter]->filename == filename ){
                        say("Displaying contents of file '%s'.\n", filename.c_str());
                        int maxlines = pdata.namelists[iter]->tokens.back().line_number;
                        printlines( filename, pdata.namelists[iter]->filecontent, 0, maxlines );
                        return true;
                    }
                }
                say("No file exists by that name."); return false; 
            }else if(iscommandinlist(target, "files")){
                preprocessordata & pdata = parent->mo->preprocdata;
                for(int iter = 0; iter < pdata.namelists.size(); iter++){
                    write("\t%s\n", pdata.namelists[iter]->filename.c_str());
                }
            }else if(iscommandinlist(target, "rpn")){
                model_diagnostics_block::instruction * instruct = ( diagblock->instructions[cinstr] );
                if( instruct->parser ){
                    ParserX & P = *(instruct->parser);
                    const mup::RPN & rpn = P.GetRPN();
                    say("Code Stack for instruction: %i, expression: '%s'", cinstr, P.GetExprAsString().c_str());
                    this->write("\tStack size: %i.\n", rpn.GetSize());
                    this->write("\tMax stack pos: %i.\n", rpn.GetRequiredStackSize() - 1);
                    const mup::token_vec_type & stack = rpn.GetData();
                    this->write("\t%3s : %5s : %s\n", "Pos", "ExPos", "Data");
                    for(int iter = 0; iter < stack.size(); iter++){
                        mup::ptr_tok_type pTok = stack[iter];
                        string ascii = pTok->AsciiDump();
                        this->write("\t%3i : %5i : %s\n", iter, pTok->GetExprPos(), ascii.c_str());
                    }
                }else{
                    say("No parser is associated with the current instruction.");
                    return false;
                } 
            }else if(iscommandinlist(target, "constants")){
                displayparserconstants( diagblock->instructions[cinstr]->parser );
            }else if(iscommandinlist(target, "stack")){
                interpretdisplaystackcommand(tokens); 
            }else if(iscommandinlist(target, "function")){
                printfunctioncallinformation(diagblock);
            }else if(iscommandinlist(target, "lines", "statements")){
                int numlines = 0; //number of lines on each side of the current one to display
                if(tokens.size() > 2){
                    sscanf(tokens[2].c_str(), "%i", &numlines);
                    numlines--;
                    if(numlines < 0) numlines = 0;
                }
                printdebuglineinformation( numlines );
            }else{
                say("Do not know how to display that.");
            }
        }
        return true;
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    void assigndebugparserinfo(ParserX & dest, const ParserX & src)
    {

    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool interpretbreakpointloopcommand(const string & input)
    {
        token_list tokens;
        extract_words( input, tokens );
        if(tokens.size() == 0) return true;
        const string & command = tokens[0]; //.word;
        if(command == "!"){ // command system
            if(tokens.size() > 1){
                string pstring = convert_expr_to_string( tokens, 1 );
                int rvalue = system(pstring.c_str());
                say("System returned value of %i.", rvalue);
            }
        }else if(iscommandinlist(command, "abort", "kill")){
            say("User requested process to abort.");
            exit(1);
        }else if(iscommandinlist(command, "break")){
            this->interpretbreakpointadd(tokens);
        }else if(iscommandinlist(command, "remove", "clear")){
            this->interpretbreakpointdelete(tokens);
        }else if(iscommandinlist(command, "xit", "quit", "exit")){
            // schedules the model to terminate on it's own, will continue but can still
            // get caught in a break point if there is one nested deeper in the call stack
            // raise a signal and continue
            parent->removeallbreakpoints();
            parent->breakpoint = false;
            parent->trap = false;
            raise(SIGTERM);
            return false;
        }else if(iscommandinlist(command, "where")){
            printfunctionstack();
        }else if(iscommandinlist(command, "display", "list")){
            this->interpretbreakpointdisplaycommand(tokens);
        }else if(iscommandinlist(command, "print")){
            ParserX P(pckNONE);
            model_diagnostics_block::instruction * instruct = ( diagblock->instructions[cinstr] );
            if(instruct->parser){ // assign data to parser
                P = *(instruct->parser);
                assigndebugparserinfo(P, *(diagblock->instructions[cinstr]->parser));
            }else{
                say("Nothing available to evaluate or display at the current position.");
            }
            token_list pstring = get_sub_list(tokens, 1, tokens.size() - 1);
            evalparser(pstring, P);
        }else if(iscommandinlist(command, "step")){
            parent->trap = true;
            parent->breakpoint = true;
            return false; // exit command loop (go on to next command);
        }else if(iscommandinlist(command, "next")){ // execute until a new line is reached
            //  obvious break point set
            parent->last_line = diagblock->instructions[cinstr]->debug_attributes.elinenumber;
            parent->trap = false;
            parent->breakpoint = true;
            return false; 
        }else if(iscommandinlist(command, "continue")){
            parent->trap = false;
            parent->breakpoint = false;
            return false; // exit loop no traps
        }else if(iscommandinlist(command, "help")){
            say("Valid commands are: ");
            this->write( "\texit\n\tnext\n\tcontinue\n\tstep\n\tbreak\n\tremove\n\tdisplay\n\tprint\n\tquit\n");
        }else{
            say("Invalid command '%s'", input.c_str());
        } 
        return true;
    }
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    bool runbreakpointloop(bool displayheader) 
    {
        string input;
        if( displayheader ){
            this->write( "CURRENT ");
            printfunctioncallinformation( diagblock );
        }
        bool loop = true;
        printdebuglineinformation( 0 );
        while( loop ){
            diagblock->mo->flushstdout();
            terminal_readline(input, " ==> ", parent->getDBGIN(), parent->getDBGOUT());
            loop = ( interpretbreakpointloopcommand(input) );
            diagblock->mo->flushstdout();
        }  
        return true;
    }

}; // end class debugger::local



// * -----------------------------------------------------------------------------------
debugger::debugger(model_class * model)
{
    mo = model;
    breakpoint = false;
    trap = false;
    last_line = -1;
}
// * -----------------------------------------------------------------------------------
void debugger::setbreakpoint()
{
    breakpoint = true;
}
// * -----------------------------------------------------------------------------------
void debugger::removebreakpoint()
{
    breakpoint = false;
}
// * -----------------------------------------------------------------------------------
bool debugger::isbreakpointon() const
{
    return breakpoint;
}
// * -----------------------------------------------------------------------------------
bool debugger::removebreakpointbyfunction(const char * fname)
{
    vector<model_diagnostics_block *> dblocks = mo->mdiags;
    for(size_t iter = 0; iter < dblocks.size(); ++iter){
        if( dblocks[iter]->isblockfunction() && dblocks[iter]->attributes.name == fname ){
            vector< model_diagnostics_block::instruction *> & instructions = dblocks[iter]->instructions;
            int pos = 0;
            while( pos < instructions.size() ){
                if( instructions[pos]->id != model_diagnostics_block::instruction::inst_jmp &&
                    instructions[pos]->id != model_diagnostics_block::instruction::inst_exit && 
                    instructions[pos]->debug_attributes.breakpointset )
                {
                    instructions[pos]->debug_attributes.breakpointset = false;
                    return true;
                }
                pos++;
            }
        }   
    }
    return false;
};
// * -----------------------------------------------------------------------------------
bool debugger::setbreakpointbyfunction(const char * fname)
{
    vector<model_diagnostics_block *> dblocks = mo->mdiags;
    for(size_t iter = 0; iter < dblocks.size(); ++iter){
        if( dblocks[iter]->isblockfunction() && dblocks[iter]->attributes.name == fname ){
            vector< model_diagnostics_block::instruction *> & instructions = dblocks[iter]->instructions;
            int pos = 0;
            while( pos < instructions.size() ){
                if( instructions[pos]->id != model_diagnostics_block::instruction::inst_jmp &&
                    instructions[pos]->id != model_diagnostics_block::instruction::inst_exit ){
                    instructions[pos]->debug_attributes.breakpointset = true;
                    return true;
                }
                pos++;
            }
        }   
    }
    return false;
};
// * -----------------------------------------------------------------------------------
bool debugger::setbreakpointbyfile(const char * namelist, int line_number)
{
    int nid = -1;
    preprocessordata & pdata = mo->preprocdata;
    const vector<namelistdata*> namelists = pdata.namelists;
    for(size_t iter = 0; iter < namelists.size(); ++iter){
        if( namelists[iter]->filename == namelist ){
            nid = static_cast<int>(iter); break;
        }
    }
    if( nid < 0 ) return false;
    // grab all diagnostics blocks from the model
    bool flagged = false;
    vector<model_diagnostics_block *> dblocks = mo->mdiags;
    for(size_t iter = 0; iter < dblocks.size(); ++iter){
        vector<model_diagnostics_block::instruction *> & instructions = dblocks[iter]->instructions;
        for(size_t pos = 0; pos < instructions.size(); ++pos){
            int inid = instructions[pos]->debug_attributes.namelist;
            int isln = instructions[pos]->debug_attributes.slinenumber;
            int ieln = instructions[pos]->debug_attributes.elinenumber; 
            if(inid == nid && ( isln <= line_number && ieln >= line_number) ){
                instructions[pos]->debug_attributes.breakpointset = true;
                flagged = true;
            }	  
        }
    }
    return flagged;
}
// * -----------------------------------------------------------------------------------
void debugger::removeallbreakpoints()
{
    vector<model_diagnostics_block *> & dblocks = mo->mdiags;
    for(size_t iter = 0; iter < dblocks.size(); ++iter){
        vector< model_diagnostics_block::instruction * > & instructions = dblocks[iter]->instructions;
        for(size_t pos = 0; pos < instructions.size(); ++pos){ 
            instructions[pos]->debug_attributes.breakpointset = false;
        }
    }
}
// * -----------------------------------------------------------------------------------
bool debugger::removebreakpointbyfile(const char * namelist, int line_number)
{
    int nid = -1;
    preprocessordata & pdata = mo->preprocdata;
    const vector<namelistdata*> namelists = pdata.namelists;
    for(size_t iter = 0; iter < namelists.size(); ++iter){
        if( namelists[iter]->filename == namelist ){
            nid = static_cast<int>(iter); break;
        }
    }
    if( nid < 0 ) return false;
    // grab all diagnostics blocks from the model
    bool removed = false;
    vector<model_diagnostics_block *> & dblocks = mo->mdiags;
    for(size_t iter = 0; iter < dblocks.size(); ++iter){
        vector<model_diagnostics_block::instruction *> & instructions = dblocks[iter]->instructions;
        for(size_t pos = 0; pos < instructions.size(); ++pos){
            int inid = instructions[pos]->debug_attributes.namelist;
            int isln = instructions[pos]->debug_attributes.slinenumber;
            int ieln = instructions[pos]->debug_attributes.elinenumber; 
            if(inid == nid && ( isln <= line_number && ieln >= line_number) && instructions[pos]->debug_attributes.breakpointset){
                instructions[pos]->debug_attributes.breakpointset = false;
                removed = true;
            }	  
        }
    }
    return removed;
}
// * -----------------------------------------------------------------------------------
void debugger::pushblock(model_diagnostics_block * block)
{
    this->cdiag.push_back(block); 
    this->breakpoint = this->trap;  // trap implies that a break point is set once a function is called or exits
    this->traps.push_back( this->trap ); // store trap status 
    this->trap = false; // the trap flag is affected by whether or not the "next" or "step" command is used
    // by default it is off 
}
// * -----------------------------------------------------------------------------------
void debugger::popblock()
{
    this->breakpoint = this->trap; // if trap is on, then we break at the next instruction after exiting the function
    this->cdiag.pop_back();
    this->trap = traps.back(); // restore previous trap status
    traps.pop_back();
}
// * -----------------------------------------------------------------------------------
const qstream & debugger::getDBGOUT() const 
{
    if( this->isdebugIOvalid() ){
        return this->DBGOUT;
    }else{
        return STDOUT;
    }
}
// * -----------------------------------------------------------------------------------
qstream & debugger::getDBGIN() 
{
    if( ! this->DBGIN.is_closed() ){
        return this->DBGIN;
    }else{
        return mo->getSTDIN(); 
    }
}
// * -----------------------------------------------------------------------------------
void debugger::closeDBGIO()
{
    this->DBGIN.close();
    this->DBGOUT.close();
}
// * -----------------------------------------------------------------------------------
bool debugger::isdebugIOvalid() const 
{
    if( this->DBGIN.is_interactive_input()  &&
        !(this->DBGOUT.is_closed()) ) return true;
    return false;
}
// * -----------------------------------------------------------------------------------
bool debugger::setDBGIN(FILE * handle)
{
    return this->DBGIN.open(handle);
}
// * -----------------------------------------------------------------------------------
bool debugger::setDBGOUT(FILE * handle)
{
    return this->DBGOUT.open(handle);
}
// * -----------------------------------------------------------------------------------
bool debugger::setDBGOUT(const char * fname)
{
    if( fname && this->DBGOUT.open(fname, "w+") ) return true;
    return false;
}
// * -----------------------------------------------------------------------------------
bool debugger::setDBGIN(const char * fname)
{
    if( fname && this->DBGIN.open(fname, "r") ) return true;
    return false;
}
// * -----------------------------------------------------------------------------------
bool debugger::filtercompiler(model_class * mo, const string & keyword, const vector<token> & tokens, const int pos)
{
    // much simpler debugger than the diag debugger
    local lcl_debugger(this, NULL, -1);
    // print initial line data
    preprocessordata & pdata = mo->preprocdata;
    int sln = tokens[pos].line_number;
    int nid = tokens[pos].namelist_number;
    namelistdata & ndata = *(pdata.namelists[nid]);
    const string & prefix = ndata.filename;
    int starredline = -1;
    lcl_debugger.printlines( prefix, ndata.filecontent, sln, sln, starredline );
    const qstream dbout(stdout);
    const qstream STDIN(stdin);
    string input; 
    while(true){
        mo->flushstdout();
        terminal_readline(input, " ==> ", STDIN, dbout);
        token_list tokens;
        extract_words( input, tokens );
        if(tokens.size() == 0) continue;
        const string & command = tokens[0];
        if(command == "!"){ // command system
            if( tokens.size() > 1 ){
                string pstring = convert_expr_to_string(tokens, 1);
                int rvalue = system(pstring.c_str());
                dbout.printf(" <== System returned value of %i.", rvalue);
            }
        }else if(iscommandinlist(command, "abort", "kill")){
            dbout.printf(" <== User requested process to abort.");
            exit(1); 
        }else if(iscommandinlist(command, "continue")){
            this->removebreakpoint();
            return true;
        }else if(iscommandinlist(command, "next")){
            return true;
        }else if(iscommandinlist(command, "print")){
            if(tokens.size() > 1){
                token_list pstring = get_sub_list(tokens, 1, tokens.size() - 1);
                ParserX p(pckNONE);
                mo->assignparserdata(p, "v", false); //assignparserdatawithmacros( mo, Pcheck, mo.getPsystem() );
                p.SetExpr(pstring);
                try { const IValue & rval = p.Eval();
                    dbout.printf(" <== "); dbout << rval; dbout.printf("\n");
                }catch(const ParserError & e){
                    dbout.printf(" <== %s\n", e.what());
                }
            } 
        }else{
            dbout.printf(" <== Unrecongized command '%s'.\n", command.c_str());
        }
    }
    return true;
}
// * -----------------------------------------------------------------------------------
bool debugger::filterblock(int cinstr, bool debug)
{
    static int lastdepth = -1;
    if( this->cdiag.size() == 0 ) return false;
    // else obtain the instruction stack
    model_diagnostics_block & dblock = *(cdiag.back());
    vector< model_diagnostics_block::instruction *> & instructions = dblock.instructions;
    if( cinstr < 0 || cinstr >= static_cast<int>(instructions.size()) ) return false;
    bool runloop = debug;
    if( instructions[cinstr]->debug_attributes.breakpointset ) runloop = true;
    if( this->breakpoint && this->last_line >= 0 ){
        int cline = instructions[cinstr]->debug_attributes.slinenumber;
        if(cline > last_line) runloop = true;
    }
    if( this->breakpoint && this->last_line < 0) runloop = true;
    if(runloop){
        this->last_line = -1;
        local lcl_debugger(this, &dblock, cinstr);
        bool displayheader = false;
        if( this->cdiag.size() != lastdepth ){
            lastdepth = cdiag.size();
            displayheader = true;
        }
        return lcl_debugger.runbreakpointloop(displayheader);
    }
    return false;
}
// * -----------------------------------------------------------------------------------
